using OpenTK;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;

namespace LightCAD.Three
{
        public class FlyControls : EventDispatcher
        {
            #region scope properties or methods
            private static EventArgs _changeEvent = new EventArgs { type = "change" };
            private static void contextmenu(EventArgs _event)
            {
                //_event.preventDefault();

            }
            #endregion

            #region Properties

            public Object3D _object;
            public GLControl domElement;
            public double movementSpeed;
            public double rollSpeed;
            public bool dragToLook;
            public bool autoForward;
            public Quaternion tmpQuaternion;
            public int status;
            public MoveState moveState;
            public Vector3 moveVector;
            public Vector3 rotationVector;
            public double movementSpeedMultiplier;
            private Vector3 lastPosition;
            private Quaternion lastQuaternion;
            private FlyControls scope;
            private double EPS;
            #endregion
            public sealed class MoveState
            {
                public double up, down, left, right, forward, back, pitchUp, pitchDown, yawLeft, yawRight, rollLeft, rollRight;
            }

            public FlyControls(Object3D _object, GLControl domElement) : base()
            {
                this._object = _object;
                this.domElement = domElement;
                // API
                this.movementSpeed = 1.0;
                this.rollSpeed = 0.005;
                this.dragToLook = false;
                this.autoForward = false;
                // disable default target object behavior
                // internals
                this.scope = this;
                this.EPS = 0.000001;
                this.lastQuaternion = new Quaternion();
                this.lastPosition = new Vector3();
                this.tmpQuaternion = new Quaternion();
                this.status = 0;
                this.moveState = new MoveState { up = 0, down = 0, left = 0, right = 0, forward = 0, back = 0, pitchUp = 0, pitchDown = 0, yawLeft = 0, yawRight = 0, rollLeft = 0, rollRight = 0 };
                this.moveVector = new Vector3(0, 0, 0);
                this.rotationVector = new Vector3(0, 0, 0);
                domElement.KeyDown += keydown;
                domElement.KeyUp += keyup;
                domElement.MouseDown += pointerdown;
                domElement.MouseMove += pointermove;
                domElement.MouseUp += pointerup;
                //this.domElement.addEventListener("contextmenu", contextmenu);
                this.updateMovementVector();
                this.updateRotationVector();
            }
            private void keydown(object sender, KeyEventArgs e)
            {
                if (e.Alt)
                {
                    return;
                }
                switch (e.KeyCode)
                {
                    case Keys.LShiftKey:
                    case Keys.RShiftKey: this.movementSpeedMultiplier = .1; break;
                    case Keys.W: this.moveState.forward = 1; break;
                    case Keys.S: this.moveState.back = 1; break;
                    case Keys.A: this.moveState.left = 1; break;
                    case Keys.D: this.moveState.right = 1; break;
                    case Keys.R: this.moveState.up = 1; break;
                    case Keys.F: this.moveState.down = 1; break;
                    case Keys.Up: this.moveState.pitchUp = 1; break;
                    case Keys.Down: this.moveState.pitchDown = 1; break;
                    case Keys.Left: this.moveState.yawLeft = 1; break;
                    case Keys.Right: this.moveState.yawRight = 1; break;
                    case Keys.Q: this.moveState.rollLeft = 1; break;
                    case Keys.E: this.moveState.rollRight = 1; break;
                }
                this.updateMovementVector();
                this.updateRotationVector();
            }
            private void keyup(object sender, KeyEventArgs e)
            {
                switch (e.KeyCode)
                {
                    case Keys.LShiftKey:
                    case Keys.RShiftKey: this.movementSpeedMultiplier = 1; break;
                    case Keys.W: this.moveState.forward = 0; break;
                    case Keys.S: this.moveState.back = 0; break;
                    case Keys.A: this.moveState.left = 0; break;
                    case Keys.D: this.moveState.right = 0; break;
                    case Keys.R: this.moveState.up = 0; break;
                    case Keys.F: this.moveState.down = 0; break;
                    case Keys.Up: this.moveState.pitchUp = 0; break;
                    case Keys.Down: this.moveState.pitchDown = 0; break;
                    case Keys.Left: this.moveState.yawLeft = 0; break;
                    case Keys.Right: this.moveState.yawRight = 0; break;
                    case Keys.Q: this.moveState.rollLeft = 0; break;
                    case Keys.E: this.moveState.rollRight = 0; break;
                }
                this.updateMovementVector();
                this.updateRotationVector();
            }
            private void pointerdown(object sender, MouseEventArgs e)
            {
                if (this.dragToLook)
                {
                    this.status++;
                }
                else
                {
                    switch (e.Button)
                    {
                        case MouseButtons.Left: this.moveState.forward = 1; break;
                        case MouseButtons.Right: this.moveState.back = 1; break;
                    }
                    this.updateMovementVector();
                }
            }
            private void pointermove(object sender, MouseEventArgs e)
            {
                if (!this.dragToLook || this.status > 0)
                {
                    var container = this.getContainerDimensions();
                    var halfWidth = container.size[0] / 2.0;
                    var halfHeight = container.size[1] / 2.0;
                    this.moveState.yawLeft = -((e.X - container.offset[0]) - halfWidth) / halfWidth;
                    this.moveState.pitchDown = ((e.Y - container.offset[1]) - halfHeight) / halfHeight;
                    this.updateRotationVector();
                }
            }
            private void pointerup(object sender, MouseEventArgs e)
            {
                if (this.dragToLook)
                {
                    this.status--;
                    this.moveState.yawLeft = this.moveState.pitchDown = 0;
                }
                else
                {
                    switch (e.Button)
                    {
                        case MouseButtons.Left: this.moveState.forward = 0; break;
                        case MouseButtons.Right: this.moveState.back = 0; break;
                    }
                    this.updateMovementVector();
                }
                this.updateRotationVector();
            }
            public void update(double delta)
            {
                var moveMult = delta * this.movementSpeed;
                var rotMult = delta * this.rollSpeed;
                scope._object.translateX(scope.moveVector.x * moveMult);
                scope._object.translateY(scope.moveVector.y * moveMult);
                scope._object.translateZ(scope.moveVector.z * moveMult);
                scope.tmpQuaternion.set(scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1).normalize();
                scope._object.quaternion.multiply(scope.tmpQuaternion);
                if (
                    lastPosition.distanceToSquared(scope._object.position) > EPS ||
                    8 * (1 - lastQuaternion.dot(scope._object.quaternion)) > EPS
                )
                {
                    scope.dispatchEvent(_changeEvent);
                    lastQuaternion.copy(scope._object.quaternion);
                    lastPosition.copy(scope._object.position);
                }
            }
            private void updateMovementVector()
            {
                var forward = (this.moveState.forward > 0 || (this.autoForward && !(this.moveState.back > 0))) ? 1 : 0;
                this.moveVector.x = (-this.moveState.left + this.moveState.right);
                this.moveVector.y = (-this.moveState.down + this.moveState.up);
                this.moveVector.z = (-forward + this.moveState.back);
                //console.log( "move:", [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
            }
            private void updateRotationVector()
            {
                this.rotationVector.x = (-this.moveState.pitchDown + this.moveState.pitchUp);
                this.rotationVector.y = (-this.moveState.yawRight + this.moveState.yawLeft);
                this.rotationVector.z = (-this.moveState.rollRight + this.moveState.rollLeft);
                //console.log( "rotate:", [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
            }
            public sealed class ContainerDimensions
            {
                public double[] size;
                public double[] offset;
            }

            private ContainerDimensions getContainerDimensions()
            {
                return new ContainerDimensions
                {
                    size = new double[] { domElement.Width, domElement.Height },
                    offset = new double[] { domElement.Location.X, -domElement.Parent.Height - (domElement.Location.Y - domElement.Height) }
                };
            }
            public void dispose()
            {
                //this.domElement.removeEventListener("contextmenu", contextmenu);
                this.domElement.MouseDown -= pointerdown;
                this.domElement.MouseMove -= pointermove;
                this.domElement.MouseUp -= pointerup;
                this.domElement.KeyDown -= keydown;
                this.domElement.KeyUp -= keyup;
            }
        }
}
